/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */
#include "PlayerController.h"
#include "CameraObject.h"

namespace PQClient
{
#define NUM_ANIMS 13           // number of animations the character has
#define CHAR_HEIGHT 5          // height of character's center of mass above ground
#define CAM_HEIGHT 20           // height of camera above character's center of mass
#define RUN_SPEED 17           // character running speed in units per second
#define TURN_SPEED 500.0f      // character turning in degrees per second
#define ANIM_FADE_SPEED 7.5f   // animation crossfade speed in % of full weight per second
#define JUMP_ACCEL 30.0f       // character jump acceleration in upward units per squared second
#define GRAVITY 90.0f          // gravity in downward units per squared second
#define OFF_GROUND 0.01f //character's feet off the ground
#define MIDDLE_PIVOT_RADIUS 50.0f

	PlayerController::PlayerController(PQEngine::SceneObject *sceneObject)
		: _sceneObject(sceneObject)
	{
		keyDirection=Ogre::Vector3(0,0,0);
		goalDirection=Ogre::Vector3(0,0,0);

		Ogre::SceneManager* mgr=_sceneObject->getSceneManager();
		_rayQuery=mgr->createRayQuery(Ogre::Ray());

		_playerUnit=new PlayerUnit("Destroyer",sceneObject,Unit::Unit_Player_Destroyer);
		_playerUnit->setScale(8,8,8);
		_playerUnit->setPosition(612.902,206.419,1524.02);

		_playerUnit->setVisible(true);

		
		setAnims();
		

		keepOnGround();
		setCamera();

	}

	void PlayerController::update( float deltaT )
	{
		updateBody(deltaT);
		//updateAnimations(deltaTime);
		updateCamera(deltaT);
		
	}

	void PlayerController::setCamera()
	{
		Ogre::SceneManager* mgr=_sceneObject->getSceneManager();
		PQEngine::CameraObject *camera=_sceneObject->getCameraObject();

		//camera->setNearClipDistance(1.0f);

		camera->setFarClipDistance(99999*9);


		// create a pivot at roughly the character's shoulder
		_cameraPivot=mgr->getRootSceneNode()->createChildSceneNode();
		// this is where the camera should be soon, and it spins around the pivot
		_cameraGoal = _cameraPivot->createChildSceneNode(Ogre::Vector3(0, 0, MIDDLE_PIVOT_RADIUS));


		Ogre::Vector3 pos=_playerUnit->getPosition();

		_cameraPivot->setPosition(pos + Ogre::Vector3::UNIT_Y * CAM_HEIGHT);

		_cameraNode=camera->getCameraNode();
		_cameraNode->setPosition(_cameraPivot->getPosition() + _cameraGoal->getPosition());
		_cameraPivot->setFixedYawAxis(true);
		_cameraGoal->setFixedYawAxis(true);
		_cameraNode->setFixedYawAxis(true);
		pivotPitch = 0;



	}


	void PlayerController::keepOnGround()
	{
		if(_playerUnit){
			_rayQuery->setRay(Ogre::Ray(_playerUnit->getPosition()+Ogre::Vector3(0,-2000,0),Ogre::Vector3::UNIT_Y));
			Ogre::RaySceneQueryResult &result=_rayQuery->execute();
			Ogre::RaySceneQueryResult::iterator i=result.begin();
			if(i!=result.end()&&i->worldFragment){
				//float height=_playerUnit->getMeshObject()->getBoundingBoxSize().y/2;
				_playerUnit->setPosition(_playerUnit->getPosition().x,i->worldFragment->singleIntersection.y+0.2,_playerUnit->getPosition().z);

			}

		}
	}

	void PlayerController::updateCamera( float deltaT )
	{
		// place the camera pivot roughly at the character's shoulder
		Ogre::Vector3 pos=_playerUnit->getPosition();

		//goal node is a child of camera pivot node.
		_cameraPivot->setPosition(pos + Ogre::Vector3::UNIT_Y * CAM_HEIGHT);
		// move the camera smoothly to the goal
		Ogre::Vector3 goalPos=_cameraGoal->_getDerivedPosition();
		Ogre::Vector3 camPos=_cameraNode->getPosition();

		Ogre::Vector3 goalOffset = goalPos - camPos;
		_cameraNode->translate(goalOffset * deltaT * 3.0f);
		// always look at the pivot
		_cameraNode->lookAt(_cameraPivot->_getDerivedPosition(), Ogre::Node::TS_WORLD);

	}

	void PlayerController::setAnims()
	{
		/*
		// this is very important due to the nature of the exported animations
			bodyEntity->getSkeleton()->setBlendMode(Ogre::ANIMBLEND_CUMULATIVE);

			Ogre::String animNames[] =
			{"IdleBase", "IdleTop", "RunBase", "RunTop", "HandsClosed", "HandsRelaxed", "DrawSwords",
			"SliceVertical", "SliceHorizontal", "Dance", "JumpStart", "JumpLoop", "JumpEnd"};

			// populate our animation list
			for (int i = 0; i < NUM_ANIMS; i++){
				anims[i] = bodyEntity->getAnimationState(animNames[i]);
				anims[i]->setLoop(true);
				fadingIn[i] = false;
				fadingOut[i] = false;
			}

			// start off in the idle state (top and bottom together)
			setBaseAnimation(ANIM_IDLE_BASE);
			setTopAnimation(ANIM_IDLE_TOP);

			// relax the hands since we're not holding anything
			anims[ANIM_HANDS_RELAXED]->setEnabled(true);

			swordsDrawn = false;
		*/
	}

	void PlayerController::injectMouseMove(const OIS::MouseEvent& evt)
	{
		// update camera goal based on mouse movement
		updateCameraGoal(-0.05f * evt.state.X.rel, -0.05f * evt.state.Y.rel, -0.0005f * evt.state.Z.rel);
	}

	void PlayerController::updateCameraGoal(Ogre::Real deltaYaw, Ogre::Real deltaPitch, Ogre::Real deltaZoom)
	{
		_cameraPivot->yaw(Ogre::Degree(deltaYaw), Ogre::Node::TS_WORLD);

		// bound the pitch
		if (!(pivotPitch + deltaPitch > 25 && deltaPitch > 0) &&
			!(pivotPitch + deltaPitch < -60 && deltaPitch < 0)){
				_cameraPivot->pitch(Ogre::Degree(deltaPitch), Ogre::Node::TS_LOCAL);
				pivotPitch += deltaPitch;
		}

		Ogre::Real dist = _cameraGoal->_getDerivedPosition().distance(_cameraPivot->_getDerivedPosition());
		Ogre::Real distChange = deltaZoom * dist;

		// bound the zoom
		if (!(dist + distChange < 0.5*MIDDLE_PIVOT_RADIUS && distChange < 0) &&
			!(dist + distChange > 2*MIDDLE_PIVOT_RADIUS && distChange > 0)){
				_cameraGoal->translate(0, 0, distChange, Ogre::Node::TS_LOCAL);
		}
	}

	void PlayerController::injectKeyDown(const OIS::KeyEvent& evt)
	{
		
		// keep track of the player's intended direction
		if (evt.key == OIS::KC_W) keyDirection.z = -1;
		else if (evt.key == OIS::KC_A) keyDirection.x = -1;
		else if (evt.key == OIS::KC_S) keyDirection.z = 1;
		else if (evt.key == OIS::KC_D) keyDirection.x = 1;

		
	}

	void PlayerController::injectKeyUp(const OIS::KeyEvent& evt)
	{
		// keep track of the player's intended direction
		if (evt.key == OIS::KC_W && keyDirection.z == -1) keyDirection.z = 0;
		else if (evt.key == OIS::KC_A && keyDirection.x == -1) keyDirection.x = 0;
		else if (evt.key == OIS::KC_S && keyDirection.z == 1) keyDirection.z = 0;
		else if (evt.key == OIS::KC_D && keyDirection.x == 1) keyDirection.x = 0;

		
	}

	void PlayerController::injectMouseDown(const OIS::MouseEvent& evt, OIS::MouseButtonID id)
	{
		
	}

	void PlayerController::updateBody( float deltaT )
	{
		goalDirection = Ogre::Vector3::ZERO;   // we will calculate this

		if (keyDirection != Ogre::Vector3::ZERO){
			// calculate actually goal direction in world based on player's key directions
			goalDirection += keyDirection.z * _cameraNode->getOrientation().zAxis();
			goalDirection += keyDirection.x * _cameraNode->getOrientation().xAxis();
			goalDirection.y = 0;
			goalDirection.normalise();

			Ogre::Quaternion toGoal = _playerUnit->getOrientation().zAxis().getRotationTo(goalDirection);

			// calculate how much the character has to turn to face goal direction
			Ogre::Real yawToGoal = toGoal.getYaw().valueDegrees();
			// this is how much the character CAN turn this frame
			Ogre::Real yawAtSpeed = yawToGoal / Ogre::Math::Abs(yawToGoal) * deltaT * TURN_SPEED;
			// reduce "turnability" if we're in midair
			//if (baseAnimID == ANIM_JUMP_LOOP) yawAtSpeed *= 0.2f;

			// turn as much as we can, but not more than we need to
			if (yawToGoal < 0) yawToGoal = std::min<Ogre::Real>(0, std::max<Ogre::Real>(yawToGoal, yawAtSpeed)); //yawToGoal = Math::Clamp<Real>(yawToGoal, yawAtSpeed, 0);
			else if (yawToGoal > 0) yawToGoal = std::max<Ogre::Real>(0, std::min<Ogre::Real>(yawToGoal, yawAtSpeed)); //yawToGoal = Math::Clamp<Real>(yawToGoal, 0, yawAtSpeed);

			_playerUnit->getMeshObject()->getSceneNode()->yaw(Ogre::Degree(yawToGoal));

			// move in current body direction (not the goal direction)
			_playerUnit->getMeshObject()->getSceneNode()->translate(0, 0, deltaT * RUN_SPEED,Ogre::Node::TS_LOCAL);
		}

		keepOnGround();
	}



}
